package com.simon.study.algorithm.shuntingyard;

import cn.hutool.core.collection.CollectionUtil;
import static com.simon.study.algorithm.shuntingyard.ShuntingYard.OP_MAP;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.NoArgsConstructor;

/**
 * <p>
 *
 * @author simon
 */
public class SimpleLexer {
    String input;
    char[] chars;

    int pos = 0;

    public SimpleLexer(String input) {
        this.input = input;
        this.chars = input.toCharArray();
    }


    public List<Token> tokens = new ArrayList<>();

    public String printTokens(List<Token> tokens){
        if( CollectionUtil.isEmpty(tokens) ) { return ""; }

        StringBuilder sb = new StringBuilder();
        for (Token token : tokens) {
            sb.append(token.lexeme);
        }
        String rst = sb.toString();
        System.out.println(rst);
        return rst;
    }



    public List<Token> parse(){
        for (;;){
            if(pos >= chars.length){ break; }
            next();
        }
        return tokens;
    }

    public Token next(){
        int sp = pos, tp = pos, ep;
        char ch = chars[pos++];

        switch (ch){
            case '0':
                ep = readDecimal(chars, pos);
                tokens.add(new Token(new String(chars, tp, ep-tp), tp, ep-1));
                pos = ep;
                break;
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                ep = readDigit(chars, pos);
                ep = readDecimal(chars, ep);
                tokens.add(new Token(new String(chars, tp, ep-tp), tp, ep-1));
                pos = ep;
                break;
            case '.':
                ep = readDecimal(chars, sp);
                tokens.add(new Token(new String(chars, tp, ep-tp), tp, ep-1));
                pos = ep;
                break;
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'g':
            case 'h':
            case 'i':
            case 'j':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'o':
            case 'p':
            case 'q':
            case 'r':
            case 's':
            case 't':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':
            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
                ep = readName(chars, pos);
                tokens.add(new Token(new String(chars, tp, ep-tp), tp, ep-1));
                pos = ep;
                break;
            case ' ':
            case '\t':
            case '\n':
            case '\r':
                break;
            default:
                Integer prior = OP_MAP.get(Character.valueOf(ch));
                if(prior == null){ throw new RuntimeException("Except operation"); }
                tokens.add(new Token(new String(chars, tp, 1), prior, tp, tp));
                break;
        }
        return tokens.get(tokens.size()-1);
    }

    @NoArgsConstructor
    public static class Token implements Serializable {
        String lexeme;
        int prior;

        int sp;
        int ep;

        public Token(String lexeme, int sp, int ep) {
            this.lexeme = lexeme;
            this.sp = sp;
            this.ep = ep;
        }

        public Token(String lexeme, int prior, int sp, int ep) {
            this.lexeme = lexeme;
            this.prior = prior;
            this.sp = sp;
            this.ep = ep;
        }
    }


    public int readDecimal(char[] chars, int sp){
        if(sp >= chars.length ){ return sp; }
        char ch = chars[sp];

        if(ch != '.'){ return sp; }

        sp++; if(sp >= chars.length){ throw new RuntimeException("except digit}"); }
        ch = chars[sp];
        if(ch < '0' || ch > '9'){ throw new RuntimeException("except digit}"); }
        sp++;
        return readDigit(chars, sp);
    }

    public int readDigit(char[] chars, int sp){
        if(sp >= chars.length ){ return sp; }
        char ch = chars[sp];
        boolean read = false;
        LOOP: for(;;){
            switch (ch) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    read = true;
                    ch = chars[sp++];
                    if(sp >= chars.length ){ return sp; }
                    break;
                default:
                    break LOOP;
            }
        }

        if(read){ sp--; }

        return sp;
    }

    public int readName(char[] chars, int sp){
        if(sp >= chars.length ){ return sp; }
        char ch = chars[sp];
        boolean read = false;
        LOOP2: for(;;){
            switch (ch){
                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                case 'g':
                case 'h':
                case 'i':
                case 'j':
                case 'k':
                case 'l':
                case 'm':
                case 'n':
                case 'o':
                case 'p':
                case 'q':
                case 'r':
                case 's':
                case 't':
                case 'u':
                case 'v':
                case 'w':
                case 'x':
                case 'y':
                case 'z':
                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                case 'G':
                case 'H':
                case 'I':
                case 'J':
                case 'K':
                case 'L':
                case 'M':
                case 'N':
                case 'O':
                case 'P':
                case 'Q':
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                case 'V':
                case 'W':
                case 'X':
                case 'Y':
                case 'Z':
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    read = true;
                    ch = chars[sp++];
                    if(sp >= chars.length ){ return sp; }
                    break;
                default:
                    break LOOP2;
            }
        }
        if(read){ sp--; }
        return sp;
    }
}
